package cn.wizzer.app.crown.modules.services.impl.order;

import cn.wizzer.app.crown.modules.models.enums.OrderColEnum;
import cn.wizzer.app.sys.modules.models.SysCurrency;
import cn.wizzer.app.sys.modules.models.SysMsg;
import cn.wizzer.app.sys.modules.models.constant.CurrencyConstant;
import cn.wizzer.app.sys.modules.services.SysCurrencyService;
import cn.wizzer.app.sys.modules.services.SysMsgService;
import cn.wizzer.app.user.modules.models.AccountRecordStatistics;
import cn.wizzer.app.user.modules.models.TrialFund;
import cn.wizzer.app.user.modules.models.UserAccount;
import cn.wizzer.app.user.modules.models.enums.UserColEnum;
import cn.wizzer.app.user.modules.services.TrialFundService;
import cn.wizzer.app.user.modules.services.account.AccountRecordStatisticsService;
import cn.wizzer.app.user.modules.services.account.UserAccountService;
import cn.wizzer.framework.util.DateUtil;
import cn.wizzer.app.crown.modules.models.CrownBet;
import cn.wizzer.app.crown.modules.models.CrownMatch;
import cn.wizzer.app.crown.modules.models.CrownOrder;
import cn.wizzer.app.crown.modules.models.constant.CrownConstant;
import cn.wizzer.app.crown.modules.models.enums.SettleStatusEnum;
import cn.wizzer.app.crown.modules.models.vo.CrownOrderVO;
import cn.wizzer.app.crown.modules.services.CrownBetService;
import cn.wizzer.app.crown.modules.services.CrownMatchService;
import cn.wizzer.app.crown.modules.services.CrownSettleService;
import cn.wizzer.app.crown.modules.services.order.CrownOrderService;
import cn.wizzer.app.sys.modules.services.SysParamService;
import cn.wizzer.app.user.modules.models.enums.BizTypeEnum;
import cn.wizzer.app.user.modules.services.account.UserAccountRecordService;
import cn.wizzer.framework.util.Result;
import cn.wizzer.framework.base.exceptions.BizException;
import cn.wizzer.framework.base.model.enums.BizExceptionEnum;
import cn.wizzer.framework.base.service.BaseServiceImpl;
import cn.wizzer.framework.base.service.CommonDBService;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.Id;
import cn.wizzer.notify.WkNotifyUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.fastjson.JSONObject;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.dao.util.cri.SqlExpressionGroup;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@IocBean(args = {"refer:dao"})
@Service(interfaceClass = CrownOrderService.class)
public class CrownOrderServiceImpl extends BaseServiceImpl<CrownOrder> implements CrownOrderService {
    public CrownOrderServiceImpl(Dao dao) {
        super(dao);
    }

    @Inject
    private CommonDBService dbService;
    @Inject
    private CrownMatchService matchService;
    @Inject
    private CrownBetService betService;
    @Inject
    private RedisService redisService;
    @Inject
    private CrownSettleService settleService;

    @Reference(retries = 0)
    @Inject
    private UserAccountRecordService recordService;

    @Reference
    @Inject
    private SysParamService sysParamService;

    @Reference
    @Inject
    private UserAccountService accountService;

    @Reference
    @Inject
    private TrialFundService trialFundService;

    @Inject
    @Reference(retries = 3, timeout = 10000)
    private SysMsgService sysMsgService;

    @Inject
    @Reference
    private WkNotifyUtil wkNotifyUtil;

    @Inject
    @Reference
    private SysCurrencyService currencyService;

    @Inject
    @Reference
    private AccountRecordStatisticsService statisticsService;

    private static final Log log = Logs.get();


    @Override
    public Pagination listPage(String userCnd, String orderCnd, String orderStatus, String betType, String matchStage,
                               String searchKeyword, String[] searchDate, int pageNumber, int pageSize) {
        StringBuffer sb = new StringBuffer();
        sb.append("SELECT m.id as matchId,m.stage,m.mid,m.home,m.custom,m.league,m.dateTime,m.hInBallFull,");
        sb.append(" m.cInBallFull,m.hInBallHalf,m.cInBallHalf,b.protect,o.*,b.type,b.betContent,u.nickName,u.userName,u.phone");
        sb.append(" FROM crown_order o");
        sb.append(" LEFT JOIN user u ON u.id = o.userId");
        sb.append(" LEFT JOIN crown_bet b ON o.betId = b.id");
        sb.append(" LEFT JOIN crown_match m ON b.matchId = m.id");
        sb.append(" where 1=1");
        // 勾选的用户条件
        if (Strings.isNotBlank(userCnd)) {
            String[] split = userCnd.split(",");
            for (String col : split) {
                String colName = UserColEnum.getValByName(col);
                if (Strings.isNotBlank(colName)) {
                    // 默认不显示游客、内部账号
                    sb.append(" and u.").append(colName).append(" = 0");
                }
            }
        }
        // 勾选的订单条件
        if (Strings.isNotBlank(orderCnd)) {
            String[] split = orderCnd.split(",");
            for (String col : split) {
                OrderColEnum enumObj = OrderColEnum.getEnumByName(col);
                if (enumObj != null) {
                    sb.append(" and ").append(enumObj.getFormName()).append(".")
                            .append(enumObj.getVal()).append(" = ").append(enumObj.getBo());
                }
            }
        }
        if (Strings.isNotBlank(orderStatus)) {
            sb.append(" and o.status='").append(orderStatus).append("'");
        }
        // 按投资内容搜索
        if (Strings.isNotBlank(betType)) {
            sb.append(" and b.type='").append(betType).append("'");
        }
        // 按赛事阶段内容搜索
        if (Strings.isNotBlank(matchStage)) {
            sb.append(" and m.stage='").append(matchStage).append("'");
        }
        // 按昵称/邮箱/订单号搜索
        if (Strings.isNotBlank(searchKeyword)) {
            sb.append(" and (");
            sb.append(" u.userName = '").append(searchKeyword).append("'");
            sb.append(" or u.phone like '%").append(searchKeyword).append("%'");
            sb.append(" or o.no = '").append(searchKeyword).append("'");
            sb.append(" or m.mid = '").append(searchKeyword).append("'");
            sb.append(")");
        }
        // 按时间搜索
        if (null != searchDate && searchDate.length != 0) {
            sb.append(" and o.ctAt >=").append(Times.d2TS(Times.D(searchDate[0])));
            sb.append(" and o.ctAt <=").append(Times.d2TS(Times.D(searchDate[1])));
        }
        sb.append(" order by o.ctAt desc");
        return dbService.listPage(CrownOrderVO.class, pageNumber, pageSize, sb.toString());
    }

    @Override
    public Map<String, BigDecimal> searchSum(String userCnd, String orderCnd, String orderStatus, String betType, String matchStage, String searchKeyword, String[] searchDate) {
        StringBuffer sb = new StringBuffer();
        sb.append("SELECT IFNULL(sum(o.amount), 0) AS amount,");
        sb.append(" IFNULL(sum(o.resultAmount), 0) AS resultAmount");
        sb.append(" FROM crown_order o");
        sb.append(" LEFT JOIN user u ON u.id = o.userId");
        sb.append(" LEFT JOIN crown_bet b ON o.betId = b.id");
        sb.append(" LEFT JOIN crown_match m ON b.matchId = m.id");
        sb.append(" where 1=1");
        // 勾选的用户条件
        if (Strings.isNotBlank(userCnd)) {
            String[] split = userCnd.split(",");
            for (String col : split) {
                String colName = UserColEnum.getValByName(col);
                if (Strings.isNotBlank(colName)) {
                    // 默认不显示游客、内部账号
                    sb.append(" and u.").append(colName).append(" = 0");
                }
            }
        }
        // 勾选的订单条件
        if (Strings.isNotBlank(orderCnd)) {
            String[] split = orderCnd.split(",");
            for (String col : split) {
                OrderColEnum enumObj = OrderColEnum.getEnumByName(col);
                if (enumObj != null) {
                    sb.append(" and ").append(enumObj.getFormName()).append(".")
                            .append(enumObj.getVal()).append(" = ").append(enumObj.getBo());
                }
            }
        }
        if (Strings.isNotBlank(orderStatus)) {
            sb.append(" and o.status='").append(orderStatus).append("'");
        }
        // 按投资内容搜索
        if (Strings.isNotBlank(betType)) {
            sb.append(" and b.type='").append(betType).append("'");
        }
        // 按赛事阶段内容搜索
        if (Strings.isNotBlank(matchStage)) {
            sb.append(" and m.stage='").append(matchStage).append("'");
        }

        // 按昵称/邮箱/订单号搜索
        if (Strings.isNotBlank(searchKeyword)) {
            sb.append(" and (");
            sb.append(" u.userName = '").append(searchKeyword).append("'");
            sb.append(" or u.phone like '%").append(searchKeyword).append("%'");
            sb.append(" or o.no = '").append(searchKeyword).append("'");
            sb.append(" or m.mid = '").append(searchKeyword).append("'");
            sb.append(")");
        }
        // 按时间搜索
        if (null != searchDate && searchDate.length != 0) {
            sb.append(" and o.ctAt >=").append(Times.d2TS(Times.D(searchDate[0])));
            sb.append(" and o.ctAt <=").append(Times.d2TS(Times.D(searchDate[1])));
        }
        Sql sql = Sqls.create(sb.toString());
        sql.setCallback(Sqls.callback.map());
        this.dao().execute(sql);
        CrownOrder sum = sql.getObject(CrownOrder.class);
        return Result.createMap("allInvestAmount", sum.getAmount(),
                "allResultAmount", sum.getResultAmount());
    }

    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public void create(String appid, CrownOrder order) {
        String currencyId = CurrencyConstant.DEFAULT;
        if (order.getExpInvest()) {
            // 体验金每天最多投3场
            if (this.count(Cnd.where("userId", "=", appid)
                    .and("expInvest", "=", true)
                    .and("ctAt", ">", Times.getTS() - 86400)) >= 3) {
                Result.breakWith(BizExceptionEnum.EXP_INVEST_UP_TO_THREE_TIMES_A_DAY);
            }
            // 如果是体验金 ，USD(EXP)查询SysCurrencyId
            SysCurrency currency = currencyService.fetch(Cnd.where("name", "=", CurrencyConstant.EXP));
            currencyId = currency.getId();
        }
        // 检验赔率
        CrownBet bet = betService.fetch(order.getBetId());
        if (null == bet.getRate() || bet.getRate().compareTo(BigDecimal.ZERO) < 1) {
            Result.breakWith(BizExceptionEnum.RATE_ERROR_BET_FAILED);
        }
        // 已完成金额 >= 招募金额时 不允许投资
        if (bet.getCompletedAmount().compareTo(bet.getRecruitAmount()) > -1) {
            Result.breakWith(BizExceptionEnum.BET_FINISHED_STOP_BET);
        }
        // 投资金额 > 招募金额时 不允许投资
        if (order.getAmount().compareTo(bet.getRecruitAmount()) > 0) {
            Result.breakWith(BizExceptionEnum.AMOUNT_TOO_LARGE_CANT_BET);
        }
        // 赛事已经结束，停止投资
        CrownMatch match = matchService.fetch(bet.getMatchId());
        if (CrownConstant.FINISHED.equals(match.getStage()) || CrownConstant.OTHER.equals(match.getStage())) {
            Result.breakWith(BizExceptionEnum.MATCH_IS_OVER);
        }
        // 系统时间大于开赛时间前5分钟，停止投资
        if (this.checkMatchTime(match.getDateTime().getTime(), +5)) {
            Result.breakWith(BizExceptionEnum.MATCH_STARTED_STOP_BET);
        }
        // 如果是保本投资，每个用户只能投一次
        if (bet.getProtect()) {
            if (this.count(Cnd.where("userId", "=", appid).and("betId", "=", order.getBetId())) > 0) {
                Result.breakWith(BizExceptionEnum.PROTECT_BET_JUST_ONCE);
            }
        }
        // 创建订单
        order.setNo(Id.createOrderNo());
        order.setRate(bet.getRate());
        order.setUserId(appid);
        order.setStatus(SettleStatusEnum.WAIT_SETTLE);
        this.insert(order);
        // 更新‘招募金额’中‘已完成’金额
        BigDecimal comAmount = null == bet.getCompletedAmount() ? BigDecimal.ZERO : bet.getCompletedAmount();
        betService.update(Chain.make("completedAmount", order.getAmount().add(comAmount)), Cnd.where("id", "=", bet.getId()));
        betService.clearCache(bet.getMatchId(), bet.getType());
        recordService.add(currencyId, appid, order.getAmount(), BizTypeEnum.INVEST, order.getId(), order.getNo());
    }

    /**
     * 当前系统时间转换美东时间后如果大于提前xx分钟的比赛时间则返回true
     *
     * @param time
     * @return
     */
    private boolean checkMatchTime(long time, int differTime) {
        String dateStr = DateUtil.sysConvertEdt(Times.nextMinute(Times.now(), differTime));
        Date preNow = DateUtil.parseDate(dateStr, DateUtil.DATE_FORMAT_PATTERN.Y_M_D_H_M_S.PATTERN);
        if (preNow != null) {
            return preNow.getTime() >= time;
        }
        return false;
    }


    @Override
    public Pagination list(String appid, SettleStatusEnum orderType, Long startTime, Long endTime, int pageNo, int pageSize) {
        // 查询order的sql
        Sql orderSql = Sqls.create("SELECT o.id,o.no,o.rate,o.amount,o.resultAmount,o.serviceFee," +
                "o.status,o.ctAt,o.result,m.id as matchId,m.home,m.custom,m.league,m.dateTime,b.protect,b.id as betId, b.type,b.betContent " +
                "FROM crown_order AS o ");
        Sql countSql = Sqls.create("SELECT COUNT(1) FROM crown_order AS o ");
        String joinSql = "LEFT JOIN crown_bet AS b on o.betId = b.id " +
                "LEFT JOIN crown_match AS m on b.matchId = m.id " +
                "$condition ";
        orderSql.appendSourceSql(joinSql);
        countSql.appendSourceSql(joinSql);
        Cnd cnd = Cnd.where("o.userId", "=", appid)
                .and("o.delFlag", "=", 0);
        // 根据订单状态(待结算、已结算、已取消)筛选
        if (null != orderType) {
            cnd.and("o.status", "=", orderType);
        }
        // 根据时间筛选
        if (null != startTime && null != endTime) {
            cnd.and("o.ctAt", ">=", startTime);
            cnd.and("o.ctAt", "<=", endTime);
        }
        orderSql.setCondition(cnd);
        orderSql.appendSourceSql("order by o.ctAt desc");
        countSql.setCondition(cnd);
        Pagination pagination = dbService.listPage(CrownOrderVO.class, pageNo, pageSize, orderSql.toString(), countSql.toString());
        // 遍历订单列表，组装玩法信息。
        List<CrownOrderVO> list = pagination.getList(CrownOrderVO.class);
        for (CrownOrderVO orderVo : list) {
            orderVo.setBetContent(orderVo.getType().NAME + " (" + orderVo.getBetContent() + ")");
        }
        return pagination;
    }


    @Override
    public synchronized void settleAuto() {
        // 查询可结算订单。可结算条件：赛事状态为finished、other并且订单状态是WAIT_SETTLE。
        // select o.* from crown_order as o left join crown_bet as b on o.betId = b.id
        // left join crown_match as m on b.matchId = m.id where (m.stage = 'finished' or m.stage = 'other')
        // and o.status = 'WAIT_SETTLE';
        Sql orderSql = Sqls.create("SELECT o.* FROM crown_order AS o LEFT JOIN crown_bet AS b ON o.betId = b.id")
                .appendSourceSql("LEFT JOIN crown_match AS m on b.matchId = m.id $condition");
        SqlExpressionGroup e1 = Cnd.exps("m.stage", "=", CrownConstant.FINISHED);
        SqlExpressionGroup e2 = Cnd.exps("m.stage", "=", CrownConstant.OTHER);
        SqlExpressionGroup e3 = Cnd.exps("o.status", "=", SettleStatusEnum.WAIT_SETTLE);
        Cnd cnd = Cnd.where(e1.or(e2)).and(e3);
        orderSql.setCondition(cnd);
        List<CrownOrder> list = dbService.list(CrownOrder.class, orderSql.toString());
        // 遍历集合逐条结算
        for (int i = 0; i < list.size(); i++) {
            CrownOrder order = list.get(i);
            try {
                settleService.settle(order);
            } catch (BizException e) {
                log.error(e.getRemand());
            }
        }
        // 处理超时订单
        this.expireOrder();
    }

    /**
     * 处理没有赛果超24小时未结算的订单
     */
    private void expireOrder() {
        // 如果当前时间大于订单关联的比赛开赛时间后8小时则取消订单(now > m.dateTime + 24)
        // 获取系统当前时间减24小时再转美东时间字符串
        String dateStr = DateUtil.sysConvertEdt(Times.nextHour(Times.now(), -24));
        Sql orderSql = Sqls.create("SELECT o.* FROM crown_order AS o LEFT JOIN crown_bet AS b ON o.betId = b.id")
                .appendSourceSql("LEFT JOIN crown_match AS m on b.matchId = m.id $condition");
        Cnd cnd = Cnd.where("o.status", "=", SettleStatusEnum.WAIT_SETTLE)
                .and("m.dateTime", "<", dateStr);
        orderSql.setCondition(cnd);
        List<CrownOrder> list = dbService.list(CrownOrder.class, orderSql.toString());
        list.forEach(order -> settleService.refund(order));
    }

    @Override
    public void settleManual(String orderId, String platformUid) throws BizException {
        // 结算单笔订单
        CrownOrder order = this.fetch(orderId);
        // 如果是‘待结算’状态，进行结算
        if (SettleStatusEnum.WAIT_SETTLE.equals(order.getStatus())) {
            // 设置操作人。‘操作时间’在结算程序内部有set
            order.setOpBy(platformUid);
            settleService.settle(order);
        } else {
            Result.breakWith("状态错误：" + order.getStatus() + "。不能结算");
        }

    }

    @Override
    public Map info(String userId) {
        List<CrownOrder> list = this.query(Cnd.where("userId", "=", userId));
        return Result.createMap("orderNum", list.size(),
                "unSettleAmount", this.unSettleAmount(list),
                "yesterdayEarnings", this.yesterdayEarnings(list),
                "totalInvestAmount", this.totalInvestAmount(list),
                "totalEarnings", this.totalEarnings(list));
    }

    @Override
    public CrownOrderVO detail(String id) {
        Sql orderSql = Sqls.create("SELECT b.protect," +
                "m.id AS matchId,m.league,m.home,m.custom,m.dateTime,m.stage,m.hInBallHalf,m.cInBallHalf,m.hInBallFull,m.cInBallFull," +
                "o.no,o.rate,o.status,o.serviceFee,o.amount,o.resultAmount,o.ctAt,o.settleAt," +
                "b.type,b.betContent " +
                "FROM crown_order o ");
        String joinSql = " LEFT JOIN crown_bet b ON o.betId = b.id" +
                " LEFT JOIN crown_match m ON b.matchId = m.id " + "$condition ";
        orderSql.appendSourceSql(joinSql);
        orderSql.setCondition(Cnd.where("o.id", "=", id));
        CrownOrderVO vo = dbService.fetchSql(CrownOrderVO.class, orderSql.toString());
        vo.setBetContent(vo.getType().NAME + " (" + vo.getBetContent() + ")");
        return vo;
    }

    @Override
    public BigDecimal searchSumPro(String userId) {
        StringBuffer sb = new StringBuffer();
        sb.append("select IFNULL(sum(amount), 0)");
        sb.append(" from crown_order o");
        sb.append(" left join crown_bet b on o.betId = b.id");
        sb.append(" where 1 = 1");
        sb.append(" and b.protect = '1'");
        sb.append(" and o.status = 'SETTLED'");
        sb.append(" and o.userId='").append(userId).append("'");
        Sql sql = Sqls.create(sb.toString());
        sql.setCallback(Sqls.callback.str());
        this.dao().execute(sql);
        return new BigDecimal(sql.getString()).setScale(6, BigDecimal.ROUND_DOWN);
    }


    @Override
    public void noResultOrder() {
        String dateStr = DateUtil.sysConvertEdt(Times.nextHour(Times.now(), -6));
        Sql orderSql = Sqls.create("SELECT m.*,b.matchId FROM crown_order AS o LEFT JOIN crown_bet AS b ON o.betId = b.id")
                .appendSourceSql("LEFT JOIN crown_match AS m on b.matchId = m.id $condition");
        Cnd cnd = Cnd.where("o.status", "=", SettleStatusEnum.WAIT_SETTLE)
                .and("m.dateTime", "<", dateStr);
        orderSql.setCondition(cnd).appendSourceSql("GROUP BY m.id");
        List<CrownOrderVO> list = dbService.list(CrownOrderVO.class, orderSql.toString());
        // 根据赛事mid去重
        List<String> midList = list.stream().map(CrownOrderVO::getMid).distinct().collect(Collectors.toList());
        if (midList.size() > 0) {
            // 防止集合长度过长，只取部分
            String mids = midList.toString().length() > 255 ? midList.toString().substring(0, 50) + "..." : midList.toString();
            // 给管理员发消息
            SysMsg msg = sysMsgService.createMsg("system", "赛果获取失败，mid:" + mids,
                    "赛事信息:" + list.toString(),
                    null);
            //插入通知
            sysMsgService.saveMsg(msg, null);
            //后台通知管理员
            wkNotifyUtil.notify(msg, null);
        }
    }

    @Override
    public void cancel(String id) {
        // 查询订单
        Sql sql = Sqls.create("SELECT o.*,m.dateTime FROM crown_order AS o LEFT JOIN crown_bet AS b ON o.betId = b.id")
                .appendSourceSql("LEFT JOIN crown_match AS m ON m.id = b.matchId WHERE o.id = @id")
                .setParam("id", id);
        CrownOrderVO orderVO = dbService.fetchSql(CrownOrderVO.class, sql.toString());
        // 注单5分钟以后不能撤销。如果相关赛事已经开赛不能撤销
        if (Times.getTS() - orderVO.getCtAt() > 300 || this.checkMatchTime(orderVO.getDateTime().getTime(), 0)) {
            Result.breakWith(BizExceptionEnum.FAILED_TO_INVEST);
        }
        log.info("用户手动撤销投注订单,订单no:" + orderVO.getNo());
        // 退款
        settleService.refund(orderVO);
    }

    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public void takeBack(String userId) {
        // 撤销该用户使用体验金投注的未结算的订单
        List<CrownOrder> orderList = this.query(Cnd.where("expInvest", "=", 1)
                .and("status", "=", SettleStatusEnum.WAIT_SETTLE).and("userId", "=", userId));
        orderList.forEach(order -> settleService.refund(order));
        // 体验金回收
        TrialFund trialFund = trialFundService.fetch(Cnd.where("userId", "=", userId));
        SysCurrency currency = currencyService.fetch(Cnd.where("name", "=", CurrencyConstant.EXP));
        UserAccount account = accountService.select(userId, currency.getId());
        BigDecimal sub = new BigDecimal("200");
        if (sub.compareTo(account.getBalance()) > 0) {
            sub = account.getBalance();
        }
        // 体验金余额大于0再扣钱
        log.info("超3天扣体验金200，userId:" + userId);
        if (sub.compareTo(BigDecimal.ZERO) > 0) {
            recordService.add(currency.getId(), userId, sub, BizTypeEnum.TRIAL_FUND_TAKE_BACK, trialFund.getId(), trialFund.getNo());
        }
        trialFund.setTrack(true);
        trialFund.setOpAt(Times.getTS());
        trialFundService.update(trialFund);
    }

    @Override
    @Aop(TransAop.REPEATABLE_READ)
    public void takeBack30(String userId) {
        // 撤销该用户使用体验金投注的未结算的订单
        List<CrownOrder> orderList = this.query(Cnd.where("expInvest", "=", 1)
                .and("status", "=", SettleStatusEnum.WAIT_SETTLE).and("userId", "=", userId));
        orderList.forEach(order -> settleService.refund(order));
        // 体验金回收
        TrialFund trialFund = trialFundService.fetch(Cnd.where("userId", "=", userId));
        SysCurrency currency = currencyService.fetch(Cnd.where("name", "=", CurrencyConstant.EXP));
        UserAccount account = accountService.select(userId, currency.getId());
        log.info("超30天体验金清零，userId:" + userId);
        if (account.getBalance().compareTo(BigDecimal.ZERO) > 0) {
            recordService.add(currency.getId(), userId, account.getBalance(), BizTypeEnum.TRIAL_FUND_EXCEED_30DAYS, trialFund.getId(), trialFund.getNo());
        }
        trialFund.setProfit30(true);
        trialFund.setOpAt(Times.getTS());
        trialFundService.update(trialFund);
    }

    @Override
    public void expInvestStatistics(Long startDate, Long endDate) {
        // SELECT o.* FROM crown_order AS o LEFT JOIN user AS u on u.id = o.userId
        // WHERE u.inside = 0 AND o.expInvest = 1 AND o.ctAt >= 1618848000 AND o.ctAt < 1618934400;
        log.info("统计体验金投资人数,startDate:" + startDate + "。endDate:" + endDate);
        Sql sql = Sqls.create("SELECT o.* FROM crown_order AS o LEFT JOIN user AS u on u.id = o.userId")
                .appendSourceSql("WHERE u.inside = 0 AND o.expInvest = 1 AND o.ctAt BETWEEN @startDate AND @endDate")
                .setParam("startDate", startDate)
                .setParam("endDate", endDate);
        List<CrownOrder> orderList = dbService.list(CrownOrder.class, sql.toString());
        // 统计人数
        long peopleNum = orderList.stream().map(CrownOrder::getUserId).distinct().count();
        // 统计数据入库
        String date = Times.format("yyyy-MM-dd", Times.D(startDate * 1000));
        AccountRecordStatistics fetch = statisticsService.fetch(Cnd.where("date", "=", date));
        if (null != fetch) {
            fetch.setExpInvestPeopleNum(peopleNum);
            statisticsService.update(fetch);
        } else {
            log.error("统计体验金投资人数失败，fetch null,date:" + date);
        }
    }

    @Override
    public Map<String, Object> userProfit(String userId, String timezone, Long startTime, Long endTime, int pageNumber, int pageSize) {
        Pagination pagination = new Pagination();
        List<CrownOrderVO> dailyList = new ArrayList<>(pageSize);
        Cnd cnd = Cnd.where("userId", "=", userId).and("status", "=", SettleStatusEnum.SETTLED);
        if (null != startTime && null != endTime) {
            // 根据timezone把start和end转成系统所在时区的时间
            cnd.and("ctAt", ">=", DateUtil.timeZoneConvertSys(timezone, startTime));
            cnd.and("ctAt", "<=", DateUtil.timeZoneConvertSys(timezone, endTime));
        }
        // 查询该用户已结算的所有订单
        List<CrownOrder> list = this.query(cnd.desc("ctAt"));
        if (list.isEmpty()) {
            pagination.setList(dailyList);
            return Result.createMap("pagination", pagination, "sum", Result.createMap("allInvestAmount", 0, "allProfitAmount", 0));
        }
        // 总投资金额
        BigDecimal allInvestAmount = BigDecimal.ZERO;
        // 总利润金额
        BigDecimal allProfitAmount = BigDecimal.ZERO;

        // 订单集合转换成vo集合。
        List<CrownOrderVO> voList = list.stream()
                .map(order -> JSONObject.parseObject(JSONObject.toJSONString(order), CrownOrderVO.class))
                .collect(Collectors.toList());
        // 把ctAt时间戳根据时区转换成2021-02-02这种字符串，set到vo的dateStr属性
        voList.forEach(orderVO -> {
            String dateStr = DateUtil.dateFormatByTimeZone(timezone, DateUtil.DATE_FORMAT_PATTERN.Y_M_D.PATTERN, orderVO.getCtAt() * 1000);
            orderVO.setDateStr(dateStr);
        });
        // vo集合转换成日期集合并去重 ["2021-02-02,2021-02-03,2021-03-01,...."]
        List<String> dateStrList = voList.stream().map(CrownOrderVO::getDateStr).distinct().collect(Collectors.toList());
        // 总条数
        int totalCount = dateStrList.size();

        // 根据pageNumber、pageSize获取起始与结尾的下标
        int fromIndex = pageNumber * pageSize - pageSize;
        int endIndex = Math.min(totalCount, pageNumber * pageSize);

        // 根据日期集合中的日期统计该日盈亏
        BigDecimal sumAmount = BigDecimal.ZERO;
        BigDecimal sumResultAmount = BigDecimal.ZERO;
        for (int i = 0; i < dateStrList.size(); i++) {
            String dateStr = dateStrList.get(i);
            List<CrownOrderVO> collect = voList.stream().filter(vo -> vo.getDateStr().equals(dateStr)).collect(Collectors.toList());
            if (collect.size() > 0) {
                sumAmount = collect.stream().map(CrownOrderVO::getAmount).reduce(BigDecimal::add).get();
                // 总投资金额
                allInvestAmount = allInvestAmount.add(sumAmount);

                // 投资收益 = 收入 - 本金 -服务费(resultAmount里已经减掉服务费了)
                sumResultAmount = collect.stream().map(CrownOrderVO::getResultAmount).reduce(BigDecimal::add).get().subtract(sumAmount);
                // 总利润金额
                allProfitAmount = allProfitAmount.add(sumResultAmount);
            }

            // 根据下标判断当前数据是否是要返回的分页数据
            if (i >= fromIndex && i < endIndex) {
                CrownOrderVO dailyVo = new CrownOrderVO();
                dailyVo.setAmount(sumAmount);
                dailyVo.setResultAmount(sumResultAmount);
                dailyVo.setDateStr(dateStr);
                dailyList.add(dailyVo);
            }
        }

        // 分页
        pagination.setList(dailyList);
        pagination.setPageSize(pageSize);
        pagination.setPageNo(pageNumber);
        pagination.setTotalCount(totalCount);

        return Result.createMap("pagination", pagination, "sum",
                Result.createMap("allInvestAmount", allInvestAmount, "allProfitAmount", allProfitAmount));
    }

    /**
     * 未结算金额
     *
     * @param list
     * @return
     */
    private BigDecimal unSettleAmount(List<CrownOrder> list) {
        // 未结算订单集合
        list = list.stream()
                .filter(order -> SettleStatusEnum.WAIT_SETTLE.equals(order.getStatus()))
                .collect(Collectors.toList());
        return this.amountCumSum(list, "invest");
    }

    /**
     * 昨日收益
     *
     * @param list
     * @return
     */
    private BigDecimal yesterdayEarnings(List<CrownOrder> list) {
        // 获取当前时刻24小时之前的时刻
        long preTime = Times.nextHour(Times.now(), -24).getTime() / 1000L;
        list = list.stream()
                .filter(order -> order.getResult() && order.getSettleAt() >= preTime && order.getSettleAt() <= Times.getTS())
                .collect(Collectors.toList());
        return this.amountCumSum(list, "earnings");
    }


    /**
     * 总投资金额
     *
     * @param list
     * @return
     */
    private BigDecimal totalInvestAmount(List<CrownOrder> list) {
        // 过滤投资失败的订单
        list = list.stream().
                filter(order -> !SettleStatusEnum.CANCELLED.equals(order.getStatus()))
                .collect(Collectors.toList());
        return this.amountCumSum(list, "invest");
    }


    /**
     * 累计收益
     *
     * @param list
     * @return
     */
    private BigDecimal totalEarnings(List<CrownOrder> list) {
        list = list.stream()
                .filter(order -> SettleStatusEnum.SETTLED.equals(order.getStatus()))
                .collect(Collectors.toList());
        return this.amountCumSum(list, "earnings");
    }


    /**
     * @param list
     * @param amountType 金额类型：投资、收益
     * @return
     */
    private BigDecimal amountCumSum(List<CrownOrder> list, String amountType) {
        BigDecimal amount = BigDecimal.ZERO;
        if (list.size() > 0) {
            if ("invest".equals(amountType)) {
                amount = list.stream().map(CrownOrder::getAmount).reduce(BigDecimal::add).get();
            } else {
                // 投资收益 = 收入 - 本金 -服务费(resultAmount里已经减掉服务费了)
                amount = list.stream()
                        .map(order -> order.getResultAmount().subtract(order.getAmount()))
                        .reduce(BigDecimal::add).get();
            }
        }
        return amount;
    }


}
